//
//  DemoFile.swift
//  swiftDemo
//
//  Created by JIENING ZHANG on 2020/7/30.
//  Copyright © 2020 lovivid. All rights reserved.
//
// https://www.jianshu.com/p/8feb8b0df1d0

import UIKit

class DemoFile: NSObject {
    // 深度优先 递归遍历目录
    class func depthFirstLoop(path:String) {
        let manager = FileManager.default
        var isDir = ObjCBool.init(false)
        let exist = manager.fileExists(atPath: path, isDirectory: &isDir)
        if (exist) {
            if(isDir.boolValue) {
                print("\(path) is dir")
                let subList = try? manager.contentsOfDirectory(atPath: path)
                if subList != nil {
                    for name in subList! {
                        let subPath = path.appending("/").appending(name)
                        DemoFile.depthFirstLoop(path: subPath)
                    }
                }
            } else {
                print("\(path) is file")
            }
        } else {
            print("\(path) not exist")
        }
    }
    
    // 广度优先 递归遍历目录
    class func breadthFirst(path:String) {
        let manager = FileManager.default
        var isDir = ObjCBool.init(false)
        let exist = manager.fileExists(atPath: path, isDirectory: &isDir)
        if (exist) {
            print("breadthFirst: \(path) exist")
            if(isDir.boolValue) {
                let subList = try? manager.contentsOfDirectory(atPath: path)
                if subList != nil {
                    var subDirList = [String]()
                    for name in subList! {
                        let subPath = path.appending("/").appending(name)
                        manager.fileExists(atPath: subPath, isDirectory: &isDir)
                        if(isDir.boolValue) {
                            subDirList.append(subPath)
                            print("breadthFirst: \(subPath) is dir")
                        } else {
                            print("breadthFirst: \(subPath) is file")
                        }
                    }
                    
                    for subDirPath in subDirList {
                        DemoFile.breadthFirst(path: subDirPath)
                    }
                }
            } else {
                print("breadthFirst: \(path) is file")
            }
        } else {
            print("breadthFirst: \(path) not exist")
        }
    }
    
    class func textFileRead(path:String) {
        //方法1
        let readHandler = try? FileHandle(forReadingFrom:URL(fileURLWithPath:path))
        if readHandler != nil {
            let data = readHandler!.readDataToEndOfFile()
            let readString = String(data: data, encoding: String.Encoding.utf8)
            print("textFileRead,FileHandle: \(readString ?? "")")
        } else {
            print("textFileRead,FileHandle: fail to create FileHandle")
        }

        
        //方法2
        let manager = FileManager.default
        let data2 = manager.contents(atPath:path)
        if data2 != nil {
            let readString2 = String(data: data2!, encoding: String.Encoding.utf8)
            print("textFileRead,FileHandle: \(readString2 ?? "")")
        } else {
            print("textFileRead,FileHandle: fail to read")
        }
    }
    
    class func textFileWrite(path:String) {
        let poem = """
水调歌头·明月几时有
苏轼
丙辰中秋，欢饮达旦，大醉，作此篇，兼怀子由。

明月几时有？把酒问青天。
不知天上宫阙，今夕是何年。
我欲乘风归去，又恐琼楼玉宇，高处不胜寒。
起舞弄清影，何似在人间。

转朱阁，低绮户，照无眠。
不应有恨，何事长向别时圆？
人有悲欢离合，月有阴晴圆缺，此事古难全。
但愿人长久，千里共婵娟。
"""
        do {
            try poem.write(toFile: path, atomically: true, encoding: String.Encoding.utf8)
            //try poem.write(to:URL(fileURLWithPath:path), atomically:true, encoding:String.Encoding.utf8)
            print("textFileWrite:succ write:\(path)")
        } catch {
            print("textFileWrite:fail write:\(path)")
        }
    }
    
    
    class func readFromPlist() {
        let tempPath=NSTemporaryDirectory();
        let plistPath = tempPath.appending("/demo.plist")
        print("readFromPlist:plistPath:\(plistPath)")
        
        if let dicFromPlist = NSDictionary(contentsOfFile: plistPath) as? [String:AnyObject] {
            print("dicFromPlist : \(dicFromPlist)")
        } else {
             print("readFromPlist : fail to load plist as dictionary")
        }
    }
    
    class func writePlist() {
        let month = [
            "January",
            "February",
            "March",
            "April",
            "May",
            "June",
            "July",
            "August",
            "September",
            "October",
            "November",
            "December",
            ];
        let monthDic : [String : Int] = [
            "January" :     31,
            "February" :    28,
            "March" :       31,
            "April" :       30,
            "May" :         31,
            "June" :        30,
            "July" :        31,
            "August" :      31,
            "September" :   30,
            "October" :     31,
            "November" :    30,
            "December" :    31,
        ];
        
        var dic2Write = [String:AnyObject]()
        dic2Write["array"] = month as AnyObject
        dic2Write["dic"] = monthDic as AnyObject
        dic2Write["string"] = "年月日" as AnyObject
        dic2Write["number"] = 3.1416 as AnyObject
        dic2Write["bool"] = true as AnyObject
        dic2Write["date"] = Date() as AnyObject
        
        let tempPath=NSTemporaryDirectory();
        let plistPath = tempPath.appending("/demo.plist")
        print("writePlist:plistPath:\(plistPath)")
        
        let writeRes = (dic2Write as NSDictionary).write(toFile: plistPath, atomically: true)
        if(writeRes) {
            print("writePlist: succ")
        } else {
            print("writePlist: fail")
        }
    }
    
    class func writeJson() {
        let month = [
            "January",
            "February",
            "March",
            "April",
            "May",
            "June",
            "July",
            "August",
            "September",
            "October",
            "November",
            "December",
            ];
        let monthDic : [String : Int] = [
            "January" :     31,
            "February" :    28,
            "March" :       31,
            "April" :       30,
            "May" :         31,
            "June" :        30,
            "July" :        31,
            "August" :      31,
            "September" :   30,
            "October" :     31,
            "November" :    30,
            "December" :    31,
        ];
        
        var dic2Write = [String:AnyObject]()
        dic2Write["array"] = month as AnyObject
        dic2Write["dic"] = monthDic as AnyObject
        dic2Write["string"] = "年月日" as AnyObject
        dic2Write["number"] = 3.1416 as AnyObject
        dic2Write["bool"] = true as AnyObject
        // Date 对象无法变成 json 字符串
        //dic2Write["date"] = Date() as AnyObject;

        // 使用 JSONSerialization 将字典对象变成 json 字符串的 category
        let jsonString = dic2Write.ggJsonStr()
        
        if jsonString == nil {
            print("writeJson: 不能表示为json")
            return;
        }
        
        let tempPath=NSTemporaryDirectory();
        let jsonPath = tempPath.appending("/demo.json")
        print("writeJson : jsonPath:\(jsonPath)")
        
        do {
            try jsonString!.write(toFile: jsonPath, atomically: true, encoding: String.Encoding.utf8)
            //try poem.write(to:URL(fileURLWithPath:path), atomically:true, encoding:String.Encoding.utf8)
            print("writeJson:succ write:\(jsonPath)")
        } catch {
            print("writeJson:fail write:\(jsonPath)")
        }
    }
    
    class func readFromJson() {
        
        let tempPath=NSTemporaryDirectory();
        let jsonPath = tempPath.appending("/demo.json");
        print("readFromJson:jsonPath:\(jsonPath)");
        
        let manager = FileManager.default
        let jsonData = manager.contents(atPath:jsonPath)
        if jsonData == nil {
            print("readFromJson,FileManager: fail to read")
            return;
        }
        
        let jsonStr = String(data: jsonData!, encoding: String.Encoding.utf8)
        
        if jsonStr == nil {
            print("readFromJson, fail to read json string")
            return;
        }
        print("readFromJson, jsonStr: \(jsonStr!)")
        
        let jsonObj = jsonStr!.ggJsonObj();
        
        print("readFromJson, jsonObj: \(jsonObj ?? "")")
    }
    
    class func binFileWrite() {
        
        // 账目记录:
        //  账目创建时间戳(主键),   int64_t
        //  账目发生时间戳，        int64_t
        //  金额,                 double
        //  币种,                 uint8_t
        //  分类数，               uint8_t
        //  分类1,                uint64_t
        //  分类2,                uint64_t
        //  ...,
        //  分类n,                uint64_t
        //  comment字符串（utf8）字节数, uint32_t
        //  comment字符串（utf8）字节
        
        // 把账目的各变量塞进 NSMutableData
        let data2Write:NSMutableData = NSMutableData();
        
        var createTS:UInt64 = UInt64(NSDate().timeIntervalSince1970);
        var accountTS:UInt64 = createTS;
        var accountNum:Double = 32.5;
        var accountMK:UInt8 = 1;
        var accountCategoryNum:UInt8 = 3;
        var accountCategory1:UInt64 = 110;
        var accountCategory2:UInt64 = 30000;
        var accountCategory3:UInt64 = 5600000;
        
        let commentStr:String = "午饭，鸡腿+关东煮，挣了800积分";
        let commentStrData:Data? = commentStr.data(using: .utf8);
        var commentStrLen:UInt32 = 0;
        if commentStrData != nil {
            commentStrLen = UInt32(commentStrData!.count);
        }
            
        data2Write.append(&createTS, length: MemoryLayout.size(ofValue:createTS));
        data2Write.append(&accountTS, length: MemoryLayout.size(ofValue:accountTS));
        data2Write.append(&accountNum, length: MemoryLayout.size(ofValue:accountNum));
        data2Write.append(&accountMK, length: MemoryLayout.size(ofValue:accountMK));
        
        data2Write.append(&accountCategoryNum, length: MemoryLayout.size(ofValue:accountCategoryNum));
        data2Write.append(&accountCategory1, length: MemoryLayout.size(ofValue:accountCategory1));
        data2Write.append(&accountCategory2, length: MemoryLayout.size(ofValue:accountCategory2));
        data2Write.append(&accountCategory3, length: MemoryLayout.size(ofValue:accountCategory3));
        
        data2Write.append(&commentStrLen, length: MemoryLayout.size(ofValue:commentStrLen));
        if commentStrData != nil {
            data2Write.append(commentStrData!);
        }
        
        // 把账目的各变量打印出来看
        let mstr:NSMutableString = NSMutableString();
        mstr.appendFormat("账目创建时间戳(主键) %lld, ", createTS);
        mstr.appendFormat("账目发生时间戳 %lld, ", accountTS);
        mstr.appendFormat("金额 %.2f, ", accountNum);
        mstr.appendFormat("币种 %d, ", accountMK);
        mstr.appendFormat("分类数 %d, ", accountCategoryNum);
        mstr.appendFormat("分类 %llu, ", accountCategory1);
        mstr.appendFormat("分类 %llu, ", accountCategory2);
        mstr.appendFormat("分类 %llu, ", accountCategory3);
        
        mstr.appendFormat("comment字符串（utf8）字节数 %u, ", commentStrLen);
        mstr.appendFormat("comment字符串（utf8）%@, ", commentStr);

        print("binFileWrite: set account info into bin: \(mstr)")
       
        
        // 把 NSMutableData 写入文件
        let tempPath=NSTemporaryDirectory();
        let binFilePath = tempPath.appending("/demo.mcbin");
        print("binFileWrite: binFilePath:\(binFilePath)");
        
        let writeRes = data2Write.write(toFile: binFilePath, atomically: true);
        
        if writeRes {
            print("binFileWrite: 二进制 文件写入成功 \(binFilePath)");
        } else {
            print("binFileWrite: 二进制 文件写入失败 \(binFilePath)");
        }
/*
         binFileWrite: set account info into bin: 账目创建时间戳(主键) 1603902446, 账目发生时间戳 1603902446, 金额 32.50, 币种 1, 分类数 3, 分类 110, 分类 30000, 分类 5600000, comment字符串（utf8）字节数 43, comment字符串（utf8）午饭，鸡腿+关东煮，挣了800积分,
         binFileWrite: binFilePath:/Users/jieningzhang/Library/Developer/CoreSimulator/Devices/4E070AF0-9A3F-485E-8C3D-50360C78382A/data/Containers/Data/Application/D29A9CB7-514A-4100-BAA4-08B6FC134C8B/tmp//demo.mcbin
         binFileWrite: 二进制 文件写入成功 /Users/jieningzhang/Library/Developer/CoreSimulator/Devices/4E070AF0-9A3F-485E-8C3D-50360C78382A/data/Containers/Data/Application/D29A9CB7-514A-4100-BAA4-08B6FC134C8B/tmp//demo.mcbin
*/
    }
    
    class func binFileRead() {
        
        // 账目记录:
        //  账目创建时间戳(主键),   int64_t
        //  账目发生时间戳，        int64_t
        //  金额,                 double
        //  币种,                 uint8_t
        //  分类数，               uint8_t
        //  分类1,                uint64_t
        //  分类2,                uint64_t
        //  ...,
        //  分类n,                uint64_t
        //  comment字符串（utf8）字节数, uint32_t
        //  comment字符串（utf8）字节
        
        // 从文件 读取 NSData
        let tempPath=NSTemporaryDirectory();
        let binFilePath = tempPath.appending("/demo.mcbin");
        print("binFileRead: binFilePath:\(binFilePath)");
        
        let dataFromFile = try? Data.init(contentsOf: URL(fileURLWithPath: binFilePath));
        
        if(dataFromFile==nil) {
            print("binFileRead: 二进制 文件读取失败 :\(binFilePath)");
            return;
        }
        
        let nSize = dataFromFile?.count
        
        print("binFileRead: 二进制 文件读取成功，长度%lu, %@", nSize!, binFilePath);
        
        // 从NSData 依次 拷贝出 账目的 各个 信息变量
        
        var pBuffer = [UInt8](dataFromFile!);
        var pRead: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer(&pBuffer)//指针取值
        
        var createTS:UInt64 = 0;
        memcpy(&createTS, pRead,   MemoryLayout.size(ofValue:createTS));
        pRead = pRead.advanced(by: MemoryLayout.size(ofValue:createTS));
        
        var accountTS:UInt64 = 0;
        memcpy(&accountTS, pRead,  MemoryLayout.size(ofValue:accountTS));
        pRead = pRead.advanced(by: MemoryLayout.size(ofValue:accountTS));
        
        var accountNum:Double = 0.0;
        memcpy(&accountNum, pRead, MemoryLayout.size(ofValue:accountNum));
        pRead = pRead.advanced(by: MemoryLayout.size(ofValue:accountNum));
        
        var accountMK:UInt8 = 0;
        memcpy(&accountMK, pRead,  MemoryLayout.size(ofValue:accountMK));
        pRead = pRead.advanced(by: MemoryLayout.size(ofValue:accountMK));
        
        var accountCategoryNum:UInt8 = 0;
        memcpy(&accountCategoryNum, pRead, MemoryLayout.size(ofValue:accountCategoryNum));
        pRead = pRead.advanced(by:         MemoryLayout.size(ofValue:accountCategoryNum));
        
        var cateArr = [UInt64]();
        
        for loop in 0..<accountCategoryNum {
            var cate:UInt64 = 0;
            memcpy(&cate, pRead,       MemoryLayout.size(ofValue:cate));
            pRead = pRead.advanced(by: MemoryLayout.size(ofValue:cate));
            
            cateArr.append(cate)
        }
        
        var commentStrLen:UInt32 = 0;
        memcpy(&commentStrLen, pRead, MemoryLayout.size(ofValue:commentStrLen));
        pRead = pRead.advanced(by:    MemoryLayout.size(ofValue:commentStrLen));
        
        var pBegin: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer(&pBuffer)//指针取值
        let loc = pBegin.distance(to: pRead);
        
        var commentStr = "";
        if loc>0 && dataFromFile!.count > loc {
            let dataRange = loc..<dataFromFile!.count;
            let commentStrData = dataFromFile?.subdata(in:dataRange);
            if commentStrData != nil {
                commentStr = String.init(data: commentStrData!, encoding: .utf8) ?? "";
            }
        }
        
        // 把账目的各变量打印出来看
        
        let mstr:NSMutableString = NSMutableString();
        mstr.appendFormat("账目创建时间戳(主键) %lld, ", createTS);
        mstr.appendFormat("账目发生时间戳 %lld, ", accountTS);
        mstr.appendFormat("金额 %.2f, ", accountNum);
        mstr.appendFormat("币种 %d, ", accountMK);
        mstr.appendFormat("分类数 %d, ", accountCategoryNum);
        for cat in cateArr {
            mstr.appendFormat("分类 %llu, ", cat);
        }
        
        mstr.appendFormat("comment字符串（utf8）字节数 %u, ", commentStrLen);
        mstr.appendFormat("comment字符串（utf8）%@, ", commentStr);

        
        print("binFileRead :get account info from bin: \(mstr)");
/*
         binFileRead: binFilePath:/Users/jieningzhang/Library/Developer/CoreSimulator/Devices/4E070AF0-9A3F-485E-8C3D-50360C78382A/data/Containers/Data/Application/D29A9CB7-514A-4100-BAA4-08B6FC134C8B/tmp//demo.mcbin
         binFileRead: 二进制 文件读取成功，长度%lu, %@ 97 /Users/jieningzhang/Library/Developer/CoreSimulator/Devices/4E070AF0-9A3F-485E-8C3D-50360C78382A/data/Containers/Data/Application/D29A9CB7-514A-4100-BAA4-08B6FC134C8B/tmp//demo.mcbin
         binFileRead :get account info from bin: 账目创建时间戳(主键) 1603902446, 账目发生时间戳 1603902446, 金额 32.50, 币种 1, 分类数 3, 分类 110, 分类 30000, 分类 5600000, comment字符串（utf8）字节数 43, comment字符串（utf8）午饭，鸡腿+关东煮，挣了800积分,
*/
    }
    
    class func writeNSCodingObject() {

        let root = DemoNSCoding()
        root.name = "root";
        
        let left = DemoNSCoding()
        left.name = "left";
        
        let center = DemoNSCoding()
        center.name = "center";
        
        let right = DemoNSCoding()
        right.name = "right";
    
        root.cDate = Date()
        root.binData = "data from String".data(using: .utf8)
        root.children = [left, center, right];
        root.info = ["depth" : "2"];
        
        print("writeNSCodingObject, root: \(root)");
    
        let archivedData = try? NSKeyedArchiver.archivedData(withRootObject: root, requiringSecureCoding: false);
        if archivedData == nil {
            print("writeNSCodingObject: fail archivedData");
            return;
        }
        
        let tempPathURL = URL.init(fileURLWithPath: NSTemporaryDirectory())
        let objPathURL = tempPathURL.appendingPathComponent("demo.swiftObj");
        print("writeNSCodingObject: objPathURL: ", objPathURL);
        
        do {
            try archivedData!.write(to: objPathURL);
            print("writeNSCodingObject: archive文件写入成功: ", objPathURL);
        } catch {
            print("writeNSCodingObject: archive文件写入失败: ", objPathURL);
        }
    }

    class func readNSCodingObject() {
        let tempPathURL = URL.init(fileURLWithPath: NSTemporaryDirectory())
        let objPathURL = tempPathURL.appendingPathComponent("demo.swiftObj");
        print("readNSCodingObject: objPathURL:%@ ", objPathURL);
        
        let objData = try? Data.init(contentsOf: objPathURL)
        if objData == nil {
            print("readNSCodingObject: fail to read data from objPathURL: ", objPathURL);
            return;
        }
        
        let topObj = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(objData!);
        
        print("readNSCodingObject: 从文件读取oc对象成功: ", topObj!);
    }

    class func writeCodableObject() {

        let root = DemoCodable()
        root.name = "root";
        
        let left = DemoCodable()
        left.name = "left";
        
        let center = DemoCodable()
        center.name = "center";
        
        let right = DemoCodable()
        right.name = "right";
    
        root.cDate = Date()
        root.binData = "data from String".data(using: .utf8)
        root.children = [left, center, right];
        root.info = ["depth" : "2"];
        
        let tempPathURL = URL.init(fileURLWithPath: NSTemporaryDirectory())
        do {
            let jsonPathURL = tempPathURL.appendingPathComponent("demo.Codable.json");
            print("writeCodableObject: jsonPathURL: ", jsonPathURL);
            let jsonEncoder = JSONEncoder()
            jsonEncoder.outputFormatting = .prettyPrinted
            let jsonDataSave = try jsonEncoder.encode(root)
            try jsonDataSave.write(to: jsonPathURL, options: .atomic)
            
            print("json save succ");
            
        } catch let err {
            print("json encode save, err: \(err)")
        }
        
        
        do {
            let plistPathURL = tempPathURL.appendingPathComponent("demo.Codable.plist");
            print("writeCodableObject: plistPathURL: ", plistPathURL);
            let plistEncoder = PropertyListEncoder()
            
            let plistDataSave = try plistEncoder.encode(root)
            try plistDataSave.write(to: plistPathURL, options: .atomic)
            
            print("plist save succ");
        } catch let err {
            print("plist encode save, err: \(err)")
        }
        
        
        
    }
    
    class func readCodableObject() {
        let tempPathURL = URL.init(fileURLWithPath: NSTemporaryDirectory())
        do {
            let jsonPathURL = tempPathURL.appendingPathComponent("demo.Codable.json");
            print("readCodableObject: objPathURL: ", jsonPathURL);
            
            let jsonData = try Data(contentsOf: jsonPathURL)
            let objFromJson = try JSONDecoder().decode(DemoCodable.self, from: jsonData)
            print("obj from json \(objFromJson)");
            
        } catch let err {
            print("json decode, err: \(err)")
        }
        
        
        do {
            let plistPathURL = tempPathURL.appendingPathComponent("demo.Codable.plist");
            print("readCodableObject: objPathURL: ", plistPathURL);
            
            let plistData = try Data(contentsOf: plistPathURL)
            let objFromJson = try PropertyListDecoder().decode(DemoCodable.self, from: plistData)
            print("obj from plist \(objFromJson)");
            
        } catch let err {
            print("plist decode , err: \(err)")
        }
        
    }
    
    class func demoLogic() {
        
        //获取Documents文件夹目录
        let docPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0] as String
        print("docPath:\(docPath)")
        
        //获取Cache目录
        let cachePath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.cachesDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0] as String
        print("cachePath:\(cachePath)")
        
        //Library目录
        let libPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.libraryDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0] as String
        print("libPath:\(libPath)")
        
        //temp目录
        let tempPath=NSTemporaryDirectory();
        print("tempPath:\(tempPath)")
        
        // 深度优先 递归遍历目录
        DemoFile.depthFirstLoop(path: libPath)
        
        // 广度优先 递归遍历目录
        DemoFile.breadthFirst(path: libPath)

        let textFilePath = docPath.appending("/text.txt")
        print("textFilePath:\(textFilePath)")
        //NSString *textFilePath = [documentsPath stringByAppendingString:@"/text.txt"];
        
        // 文本文件读取
        DemoFile.textFileRead(path: textFilePath)
        
        // 文本文件写出
        DemoFile.textFileWrite(path:textFilePath)
        
        // plist 文件读写
        // property list objects (NSString, NSData, NSDate, NSNumber, NSArray, or NSDictionary objects).
        DemoFile.writePlist()
        DemoFile.readFromPlist()
        
        // json 文件读写
        // (NSString, NSNumber, NSArray, or NSDictionary objects).
        DemoFile.writeJson()
        DemoFile.readFromJson()

        
        // 二进制文件写出
        DemoFile.binFileWrite()
        
        // 二进制文件读取Read
        DemoFile.binFileRead()
        
        // NSCoding 对象文件读写
        DemoFile.writeNSCodingObject()
        DemoFile.readNSCodingObject()
        
        // Codable 对象文件读写
        DemoFile.writeCodableObject()
        DemoFile.readCodableObject()
    }
}
